/*
 * FLINT PARTICLE SYSTEM
 * .....................
 * 
 * Author: Richard Lord (Big Room)
 * C# Port: Ben Baker (HeadSoft)
 * Copyright (c) Big Room Ventures Ltd. 2008
 * http://flintparticles.org
 * 
 * 
 * Licence Agreement
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

using System;
using System.Windows;

using FlintSharp.Actions;
using FlintSharp.Activities;
using FlintSharp.Counters;
using FlintSharp.Easing;
using FlintSharp.Emitters;
using FlintSharp.EnergyEasing;
using FlintSharp.Initializers;
using FlintSharp.Particles;
using FlintSharp.Zones;

namespace FlintSharp.Zones
{
	/// <summary>
	/// The DiscSectorZone zone defines a section of a Disc zone. The disc
	/// on which it's based have a hole in the middle, like a doughnut.
	/// </summary>
	public class DiscSectorZone : IZone
	{
		private Point m_center;
		private float m_innerRadius;
		private float m_outerRadius;
		private float m_innerSq;
		private float m_outerSq;
		private float m_minAngle;
		private float m_maxAngle;
		private float m_minAllowed;

		private static double TWOPI = Math.PI * 2;

		/// <summary>
		/// The constructor defines a DiscSectorZone zone.
		/// </summary>
		/// <param name="center">The centre of the disc.</param>
		/// <param name="outerRadius">The radius of the outer edge of the disc.</param>
		/// <param name="innerRadius">If set, this defines the radius of the inner
		/// edge of the disc. Points closer to the center than this inner radius
		/// are excluded from the zone. If this parameter is not set then all 
		/// points inside the outer radius are included in the zone.</param>
		/// <param name="minAngle">The minimum angle, in radians, for points to be included in the zone.
		/// An angle of zero is horizontal and to the right. Positive angles are in a clockwise 
		/// direction (towards the graphical y axis). Angles are converted to a value between 0 
		/// and two times PI.</param>
		/// <param name="maxAngle">The maximum angle, in radians, for points to be included in the zone.
		/// An angle of zero is horizontal and to the right. Positive angles are in a clockwise 
		/// direction (towards the graphical y axis). Angles are converted to a value between 0 
		/// and two times PI.</param>
		public DiscSectorZone(Point center, float outerRadius, float innerRadius, float minAngle, float maxAngle)
		{
			m_center = center;
			m_innerRadius = innerRadius;
			m_outerRadius = outerRadius;
			m_innerSq = m_innerRadius * m_innerRadius;
			m_outerSq = m_outerRadius * m_outerRadius;
			m_minAngle = minAngle;
			m_maxAngle = maxAngle;

			while (m_maxAngle > TWOPI)
				m_maxAngle -= (float)TWOPI;

			while (m_maxAngle < 0)
				m_maxAngle += (float)TWOPI;

			m_minAllowed = (float)(m_maxAngle - TWOPI);

			if (minAngle == maxAngle)
				m_minAngle = m_maxAngle;
			else
				m_minAngle = Clamp(m_minAngle);
		}

		private float Clamp(float angle)
		{
			while (angle > m_maxAngle)
				angle -= (float)TWOPI;

			while (angle < m_minAllowed)
				angle += (float)TWOPI;

			return angle;
		}

		/// <summary>
		/// The centre of the disc.
		/// </summary>
		public Point Center
		{
			get { return m_center; }
			set { m_center = value; }
		}

		/// <summary>
		/// The radius of the inner edge of the disc.
		/// </summary>
		public float InnerRadius
		{
			get { return m_innerRadius; }
			set { m_innerRadius = value; m_innerSq = m_innerRadius * m_innerRadius; }
		}

		/// <summary>
		/// The radius of the outer edge of the disc.
		/// </summary>
		public float OuterRadius
		{
			get { return m_outerRadius; }
			set { m_outerRadius = value; m_outerSq = m_outerRadius * m_outerRadius; }
		}

		/// <summary>
		/// The minimum angle, in radians, for points to be included in the zone.
		/// An angle of zero is horizontal and to the right. Positive angles are in a clockwise 
		/// direction (towards the graphical y axis). Angles are converted to a value between 0 
		/// and two times PI.
		/// </summary>
		public float MinAngle
		{
			get { return m_minAngle; }
			set { m_minAngle = Clamp(value); }
		}

		/// <summary>
		/// The maximum angle, in radians, for points to be included in the zone.
		/// An angle of zero is horizontal and to the right. Positive angles are in a clockwise 
		/// direction (towards the graphical y axis). Angles are converted to a value between 0 
		/// and two times PI.
		/// </summary>
		public float MaxAngle
		{
			get { return m_maxAngle; }
			set
			{
				m_maxAngle = value;

				while (m_maxAngle > TWOPI)
					m_maxAngle -= (float)TWOPI;

				while (m_maxAngle < 0)
					m_maxAngle += (float)TWOPI;

				m_minAllowed = (float)(m_maxAngle - TWOPI);
			}
		}

		/// <summary>
		/// The contains method determines whether a point is inside the zone.
		/// This method is used by the initializers and actions that
		/// use the zone. Usually, it need not be called directly by the user.
		/// </summary>
		/// <param name="x">The x coordinate of the location to test for.</param>
		/// <param name="y">The y coordinate of the location to test for.</param>
		/// <returns>true if point is inside the zone, false if it is outside.</returns>
		public bool Contains(double x, double y)
		{
			x -= m_center.X;
			y -= m_center.Y;

			double distSq = x * x + y * y;

			if (distSq > m_outerSq || distSq < m_innerSq)
				return false;

			float angle = (float)Math.Atan2(y, x);

			angle = Clamp(angle);

			return angle >= m_minAngle;
		}

		/// <summary>
		/// The getLocation method returns a random point inside the zone.
		/// This method is used by the initializers and actions that
		/// use the zone. Usually, it need not be called directly by the user.
		/// </summary>
		/// <returns>a random point inside the zone.</returns>
		public Point GetLocation()
		{
			float rand = (float)Utils.Random.NextDouble();
			Point point = Utils.PointToPolar(m_innerRadius + (1 - rand * rand) * (m_outerRadius - m_innerRadius), m_minAngle + Utils.Random.NextDouble() * (m_maxAngle - m_minAngle));

			point.X += m_center.X;
			point.Y += m_center.Y;

			return point;
		}

		/// <summary>
		/// The getArea method returns the size of the zone.
		/// It's used by the MultiZone public class to manage the balancing between the
		/// different zones. Usually, it need not be called directly by the user.
		/// </summary>
		/// <returns>the size of the zone.</returns>
		public float GetArea()
		{
			return (float)(Math.PI * m_outerSq - Math.PI * m_innerSq);
		}
	}
}
